/*
 * Copyright (c) 2018-present, lmyooyo@gmail.com.
 *
 * This source code is licensed under the GPL license found in the
 * LICENSE file in the root directory of this source tree.
 */
package com.lmy.codec.muxer.impl

import android.media.MediaCodec
import android.media.MediaFormat
import android.media.MediaMuxer
import com.lmy.codec.pipeline.impl.EventPipeline
import com.zhg.videorecord.encoder.Encoder
import com.zhg.videorecord.encoder.impl.AudioEncoderImpl
import com.zhg.videorecord.entity.CodecContext
import com.zhg.videorecord.entity.Sample
import com.zhg.videorecord.helper.TLog
import com.zhg.videorecord.muxer.Muxer
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.ByteBuffer
import java.util.*



/**
 * Created by lmyooyo@gmail.com on 2018/3/28.
 */
class MuxerImpl(private val context: CodecContext) : Muxer {
    private val mQueue = LinkedList<Sample>()
    private val mWriteSyn = Any()
    private var mAudioPipeline = EventPipeline.create("AudioWritePipeline")
    private var mVideoPipeline = EventPipeline.create("VideoWritePipeline")
    private var muxer: MediaMuxer? = null
    var videoTrack: Int = 0
    var audioTrack: Int = 0
    private var mFrameCount: Int = 0
    private var mVideoTrackReady: Boolean = false
    private var mAudioTrackReady: Boolean = false
    private var mStart: Boolean = false
    override var onMuxerListener: Muxer.OnMuxerListener? = null
    private val isSaveRawFile=true
    private var bos: FileOutputStream?=null

    init {
        start()
    }

    private fun start() {
        mFrameCount = 0
        mVideoTrackReady = false
        mAudioTrackReady = false
        mStart = false
        //删除已存在的文件
        val file = File(context.ioContext.path!!)
        if (file.exists()) file.delete()
        muxer = MediaMuxer(file.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
        initRawFileBuff()
    }

    override fun reset() {
        mVideoPipeline.queueEvent(Runnable {
            stop()
            start()
        })
    }

    override fun onFormatChanged(encoder: Encoder, format: MediaFormat) {
        if (encoder is AudioEncoderImpl) {
            TLog.i("Add audio track")
            addAudioTrack(format)
        } else {
            TLog.i("Add video track")
            addVideoTrack(format)
        }
    }

    /**
     * 编码后的帧数据
     * For VideoEncoderImpl
     */
    override fun onSample(encoder: Encoder, info: MediaCodec.BufferInfo, data: ByteBuffer) {
        if (encoder is AudioEncoderImpl) {
            writeAudioSample(Sample.wrap(info, data))
        } else {
            writeVideoSample(Sample.wrap(info, data))
        }
    }

    private fun checkStart() {
        if ((mVideoTrackReady && context.audio.silence) || (mVideoTrackReady && mAudioTrackReady)) {
            muxer?.start()
            mStart = true
            TLog.i("Muxer start")
            onMuxerListener?.onStart()
        }
    }

    override fun addVideoTrack(format: MediaFormat) {
        try {
            videoTrack = muxer!!.addTrack(format)
        } catch (e: Exception) {
            TLog.i("Add video track failed")
            onMuxerListener?.onError(ERROR_ADD_TRACK, "Add video track failed")
            e.printStackTrace()
            return
        }
        mVideoTrackReady = true
        TLog.i("Muxer video start")
        checkStart()
    }

    override fun addAudioTrack(format: MediaFormat) {
        try {
            audioTrack = muxer!!.addTrack(format)
            TLog.i("addAudioTrack ${format.getByteBuffer("csd-0").capacity()}")
        } catch (e: Exception) {
            TLog.i("Add audio track failed")
            onMuxerListener?.onError(ERROR_ADD_TRACK, "Add audio track failed")
            e.printStackTrace()
            return
        }
        mAudioTrackReady = true
        TLog.i("Muxer audio start")
        checkStart()
    }

    override fun writeVideoSample(sample: Sample) {
        if (!mStart) return
//        debug_e("writeVideoSample ${sample.bufferInfo.presentationTimeUs}")
        ++mFrameCount
        synchronized(mWriteSyn) {
            mQueue.push(sample)
        }
        mVideoPipeline.queueEvent(Runnable {
            synchronized(mWriteSyn) {
                while (!mQueue.isEmpty()) {
                    writeSample(videoTrack, mQueue.poll())
                }
            }
        })
    }

    override fun writeAudioSample(sample: Sample) {
        if (!mStart) return
//        debug_i("writeAudioSample ${sample.bufferInfo.presentationTimeUs}")
        mAudioPipeline.queueEvent(Runnable {
            writeSample(audioTrack, sample)
        })
    }

    private fun writeSample(track: Int, sample: Sample) {
        try {
//            debug_e("write${if (videoTrack == track) "Video" else "Audio"}" +
//                    "Sample($mFrameCount, ${sample.bufferInfo.presentationTimeUs}): ${sample.bufferInfo.size}")

            muxer?.writeSampleData(track, sample.sample, sample.bufferInfo)
            if (isSaveRawFile&&videoTrack==track){

                writeRawFileBuff(sample)
            }

        } catch (e: Exception) {
            onMuxerListener?.onError(ERROR_WRITE, "Write sample failed")
            e.printStackTrace()
        }
    }

    override fun release() {
        TLog.i("Muxer release")
        mVideoPipeline.quit()
        mAudioPipeline.quit()
        stop()
    }

    private fun stop() {
        mStart = false
        try {
            muxer?.release()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        }
        closeRawFileBuff()
    }

    companion object {
        const val ERROR_ADD_TRACK = 0x200
        const val ERROR_WRITE = 0x201
    }

    private fun initRawFileBuff(){
        if (isSaveRawFile){
            this.bos = FileOutputStream(
                    File(context.ioContext.path?.replace(".mp4",".h264")), false)
        }

    }
    private fun closeRawFileBuff(){
        if (isSaveRawFile){
            try {
                bos?.flush()
                bos?.close()
            }catch (e:java.lang.Exception){
                e.printStackTrace()
            }
        }


    }
    private fun writeRawFileBuff(sample: Sample){
        if (isSaveRawFile){
            try {
                /*val bytes = ByteArray(sample.bufferInfo.size)
                sample.sample.get(bytes,0,bytes.size)*/

                val fileChannel = bos?.channel
                fileChannel?.write(sample.sample)

            }catch (e:IOException){
                e.printStackTrace()
            }
        }
    }
    // 深度拷贝方法
    private fun  clone(original:ByteBuffer):ByteBuffer{
       var clone= ByteBuffer.allocate(original.capacity())
        // 使缓冲区准备好重新读取已经包含的数据：它保持限制不变，并将位置设置为零。
        // 因为 put(buffer) 会在内部遍历buffer，如果不执行rewind，position值将不会被重置
        original.rewind()
        clone.put(original)
        original.rewind()
        // 这个是将clone转换为读的这状态，否则将无法读取出数据
        clone.flip()
        return clone
    }


}